package ru.ifmo.automaton.generators.random;

import org.uncommons.maths.number.NumberGenerator;
import ru.ifmo.automaton.Automaton;
import ru.ifmo.automaton.AutomatonImp;
import ru.ifmo.automaton.SynchronizingAutomaton;
import ru.ifmo.automaton.SynchronizingAutomatonImpl;
import ru.ifmo.automaton.generators.AbstractAutomatonSource;
import ru.ifmo.common.Source;

import java.util.*;

/**
 * @author Roman Kolganov
 *         09.12.11
 */
public class RandomSynchronizingAutomatonGenerator extends AbstractAutomatonSource<Character, String> {
    private final NumberGenerator<Integer> statesCountGenerator;
    private final NumberGenerator<Integer> alphabetSizeGenerator;
    private int statesCount = 0;
    private int alphabetSize = 0;
    private List<Character> alphabet;
    private Map<String, Map<Character, String>> transitions;

    public RandomSynchronizingAutomatonGenerator(NumberGenerator<Integer> statesCountGenerator, NumberGenerator<Integer> alphabetSizeGenerator) {
        this.statesCountGenerator = statesCountGenerator;
        this.alphabetSizeGenerator = alphabetSizeGenerator;
    }

    private static String state(int state) {
        return String.valueOf(state);
    }

    @Override
    public void rebuild() {
        alphabetSize = alphabetSizeGenerator.nextValue();
        statesCount = statesCountGenerator.nextValue();

        Random random = new Random();
        alphabet = generateAlphabet(alphabetSize);
        transitions = generateTransitions(statesCount, alphabet, random);

        synchronizeTransitions(statesCount, alphabet, random, transitions);
    }

    private static void synchronizeTransitions(int statesCount, List<Character> alphabet, Random random,
                                               Map<String, Map<Character, String>> transitions) {
        int synchronizingWordLength = random.nextInt(statesCount * statesCount * statesCount);
        Character[] synchronizingWord = new Character[synchronizingWordLength];
        for (int i = 0; i < synchronizingWordLength; i++) {
            synchronizingWord[i] = alphabet.get(random.nextInt(alphabet.size()));
        }
        Character lastSynchronizingLetter = alphabet.get(random.nextInt(alphabet.size()));
        String synchronizingState = state(random.nextInt(statesCount));
        Automaton<Character, String> automaton = new AutomatonImp<Character, String>(
                transitions,
                new HashSet<String>(),
                state(0),
                null,
                alphabet
        );
        for (String state : automaton) {
            automaton.goToState(state);
            transitions.get(automaton.step(synchronizingWord)).put(lastSynchronizingLetter, synchronizingState);
        }
    }

    private static Map<String, Map<Character, String>> generateTransitions(int statesCount, List<Character> alphabet, Random random) {
        Map<String, Map<Character, String>> transitions =
                new HashMap<String, Map<Character, String>>(statesCount + 1, 1.0f);
        for (int i = 0; i < statesCount; i++) {
            Map<Character, String> stateTransitions = new HashMap<Character, String>(alphabet.size() + 1, 1.0f);
            for (Character symbol : alphabet) {
                stateTransitions.put(symbol, state(random.nextInt(statesCount)));
            }
            transitions.put(state(i), stateTransitions);
        }
        return transitions;
    }

    private static final List<Character> maxAlphabet;

    static {
        maxAlphabet = new ArrayList<Character>();
        for (char c : "abcdefghijklmnopqrstuvwxyz".toCharArray()) {
            maxAlphabet.add(c);
        }
    }

    private static List<Character> generateAlphabet(int size) {
        return maxAlphabet.subList(0, Math.min(size, maxAlphabet.size()));
    }

    @Override
    protected boolean needToRebuild() {
        return alphabetSize != alphabetSizeGenerator.nextValue() || statesCount != statesCountGenerator.nextValue();
    }

    @Override
    protected List<Character> getAlphabet() {
        return alphabet;
    }

    @Override
    protected String getInitial() {
        return state(0);
    }

    @Override
    protected Map<String, Map<Character, String>> getTransitions() {
        return transitions;
    }
}
